<template>
  <div class="viewer-image">
    <slot v-if="loading" name="placeholder">
      <div class="viewer-image__placeholder">加载中...</div>
    </slot>
    <slot v-else-if="error" name="error">
      <div class="viewer-image__error">加载失败</div>
    </slot>
    <img
      v-else
      class="viewer-image__inner"
      v-bind="$attrs"
      v-on="$listeners"
      :src="src"
      @click="clickHandler"
      :style="imageStyle"
      :class="{ 'viewer-image__inner--center': alignCenter, 'viewer-image__preview': preview }"
    />
  </div>
</template>

<script>
import { on, off, getScrollContainer, isInContainer } from './utils/dom'
import { isString, isHtmlElement } from './utils/types'
import loadsh from 'lodash'
/**
 * Parser 封装img组件 使用v-view大图预览 使用方法和element image一致 https://github.com/fengyuanchen/viewerjs#events
 * @Author Answer™
 * @Version 1.0.0
 * @Date：2022-8-22 11:21:24
 * @LastEditors：Answer™
 * @LastEditTime：2022-8-22 11:21:26
 */
const isSupportObjectFit = () => document.documentElement.style.objectFit !== undefined

const ObjectFit = {
  NONE: 'none',
  CONTAIN: 'contain',
  COVER: 'cover',
  FILL: 'fill',
  SCALE_DOWN: 'scale-down'
}

export default {
  name: 'ViewerImage',
  inheritAttrs: false,
  props: {
    src: { type: String, default: '' },
    fit: { type: String, default: '' },
    lazy: { type: Boolean, default: true },
    scrollContainer: {
      type: Object,
      default: () => {}
    },
    previewSrcList: {
      type: Array,
      default: () => []
    },
    zIndex: {
      type: Number,
      default: 2000
    }
  },
  data () {
    return {
      loading: true,
      error: false,
      show: !this.lazy,
      imageWidth: 0,
      imageHeight: 0,
      showViewer: false
    }
  },
  computed: {
    imageStyle () {
      const { fit } = this
      if (!this.$isServer && fit) {
        return isSupportObjectFit() ? { 'object-fit': fit } : this.getImageStyle(fit)
      }
      return {}
    },
    alignCenter () {
      return !this.$isServer && !isSupportObjectFit() && this.fit !== ObjectFit.FILL
    },
    preview () {
      const { previewSrcList } = this
      return Array.isArray(previewSrcList) && previewSrcList.length > 0
    },
    imageIndex () {
      let previewIndex = 0
      const srcIndex = this.previewSrcList.indexOf(this.src)
      if (srcIndex >= 0) {
        previewIndex = srcIndex
      }
      return previewIndex
    }
  },

  watch: {
    src (val) {
      this.show && this.loadImage()
    },
    show (val) {
      val && this.loadImage()
    }
  },

  mounted () {
    if (this.lazy) {
      this.addLazyLoadListener()
    } else {
      this.loadImage()
    }
  },

  beforeDestroy () {
    this.lazy && this.removeLazyLoadListener()
  },

  methods: {
    loadImage () {
      if (this.$isServer) return
      // reset status
      this.loading = true
      this.error = false

      const img = new Image()
      img.onload = (e) => this.handleLoad(e, img)
      img.onerror = this.handleError.bind(this)

      // bind html attrs
      // so it can behave consistently
      Object.keys(this.$attrs).forEach((key) => {
        const value = this.$attrs[key]
        img.setAttribute(key, value)
      })
      img.src = this.src
    },
    handleLoad (e, img) {
      this.imageWidth = img.width
      this.imageHeight = img.height
      this.loading = false
      this.error = false
    },
    handleError (e) {
      this.loading = false
      this.error = true
      this.$emit('error', e)
    },
    handleLazyLoad () {
      if (isInContainer(this.$el, this._scrollContainer)) {
        this.show = true
        this.removeLazyLoadListener()
      }
    },
    addLazyLoadListener () {
      if (this.$isServer) return

      const { scrollContainer } = this
      let _scrollContainer = null

      if (isHtmlElement(scrollContainer)) {
        _scrollContainer = scrollContainer
      } else if (isString(scrollContainer)) {
        _scrollContainer = document.querySelector(scrollContainer)
      } else {
        _scrollContainer = getScrollContainer(this.$el)
      }

      if (_scrollContainer) {
        this._scrollContainer = _scrollContainer
        this._lazyLoadHandler = loadsh.throttle(this.handleLazyLoad, 200)
        on(_scrollContainer, 'scroll', this._lazyLoadHandler)
        this.handleLazyLoad()
      }
    },
    removeLazyLoadListener () {
      const { _scrollContainer, _lazyLoadHandler } = this

      if (this.$isServer || !_scrollContainer || !_lazyLoadHandler) return

      off(_scrollContainer, 'scroll', _lazyLoadHandler)
      this._scrollContainer = null
      this._lazyLoadHandler = null
    },
    /**
     * simulate object-fit behavior to compatible with IE11 and other browsers which not support object-fit
     */
    getImageStyle (fit) {
      const { imageWidth, imageHeight } = this
      const { clientWidth: containerWidth, clientHeight: containerHeight } = this.$el

      if (!imageWidth || !imageHeight || !containerWidth || !containerHeight) return {}

      const imageAspectRatio = imageWidth / imageHeight
      const containerAspectRatio = containerWidth / containerHeight

      if (fit === ObjectFit.SCALE_DOWN) {
        const isSmaller = imageWidth < containerWidth && imageHeight < containerHeight
        fit = isSmaller ? ObjectFit.NONE : ObjectFit.CONTAIN
      }

      switch (fit) {
        case ObjectFit.NONE:
          return { width: 'auto', height: 'auto' }
        case ObjectFit.CONTAIN:
          return imageAspectRatio < containerAspectRatio ? { width: 'auto' } : { height: 'auto' }
        case ObjectFit.COVER:
          return imageAspectRatio < containerAspectRatio ? { height: 'auto' } : { width: 'auto' }
        default:
          return {}
      }
    },
    clickHandler () {
      if (!this.preview) return
       this.$viewerApi({
        options: {
          initialViewIndex: this.imageIndex,
          // zIndex: this.zIndex,
          // zIndexInline: this.zIndex,
          hidden: (e) => {
            this.$emit('hidden')
          }
        },
        images: this.previewSrcList
      })
    }
  }
}
</script>
<style lang="less" scoped>
@import './style.less';
.viewer-container {
    z-index: 9999;
}
</style>
